package be.sc.classicnetwork.dao.hibernate;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.springframework.dao.support.DataAccessUtils;

import be.sc.classicnetwork.dao.CarDao;
import be.sc.classicnetwork.dao.PaginationSearchResult;
import be.sc.classicnetwork.model.Car;
import be.sc.classicnetwork.model.Contact;
import be.sc.classicnetwork.model.Image;
import be.sc.classicnetwork.model.code.Make;

/**
 * CarDaoImpl.
 */
public class CarDaoImpl extends HibernateGenericDao<Car> implements CarDao {

    private static final Order DEFAULT_ORDER = Order.asc("mk.name");

    private static final Map<String, String> ALIASES;

    static {
	ALIASES = new HashMap<String, String>();
	ALIASES.put("make", "mk");
    }

    public CarDaoImpl() {
	super();
    }

    public CarDaoImpl(Class<Car> clazz) {
	super(clazz);
    }

    /**
     * @see be.sc.classicnetwork.dao.CarDao#findAll(int, int, boolean)
     */
    public PaginationSearchResult findAll(int first, int max, boolean enableFilterPublished) {
	PaginationSearchResult result = new PaginationSearchResult();
	if (enableFilterPublished) {
	    getHibernateTemplate().enableFilter(Car.PUBLISHED_FILTER);
	}
	result.setList(this.findByCriteria(first, max, ALIASES, null, new Order[] { DEFAULT_ORDER }));
	result.setCount(countAll());
	return result;
    }

    /**
     * @see be.sc.classicnetwork.dao.CarDao#countMakes(boolean)
     */
    @SuppressWarnings("unchecked")
    public Collection<Object[]> countMakes(boolean enableFilterPublished) {
	if (enableFilterPublished) {
	    getHibernateTemplate().enableFilter(Car.PUBLISHED_FILTER);
	}
	return getHibernateTemplate()
		.find(
			"select  car.make.name, count(*), car.make.id from Car car group by car.make.name, car.make.id order by car.make.name");
    }

    /**
     * @see be.sc.classicnetwork.dao.CarDao#findByMake(int, int, boolean,
     *      be.sc.classicnetwork.model.code.Make)
     */
    public PaginationSearchResult findByMake(int first, int max, boolean enableFilterPublished, Make make) {
	PaginationSearchResult result = new PaginationSearchResult();
	if (enableFilterPublished) {
	    getHibernateTemplate().enableFilter(Car.PUBLISHED_FILTER);
	}
	Criterion criterion = Restrictions.eq("make", make);
	result
		.setList(findByCriteria(first, max, ALIASES, new Criterion[] { criterion },
			new Order[] { DEFAULT_ORDER }));
	result.setCount(countByCriteria(null, criterion));
	return result;
    }

    /**
     * Used by findByModelOrMakeLike here under.
     */
    private DetachedCriteria getFindByModelOrMakeLikeCriteria(String model) {
	// split the query string and make a criterion of each token on
	// make or model
	String[] tokens = model.split(" ");

	// set the criteria
	DetachedCriteria criteria = DetachedCriteria.forClass(Car.class);
	criteria.createAlias("make", "mk");
	for (String s : tokens) {
	    criteria.add(Restrictions.or(Restrictions.ilike("model", s, MatchMode.ANYWHERE), Restrictions.ilike(
		    "mk.name", s, MatchMode.ANYWHERE)));
	}

	return criteria;
    }

    /**
     * @see be.sc.classicnetwork.dao.CarDao#findByModelOrMakeLike(int, int,
     *      boolean, String)
     */
    @SuppressWarnings("unchecked")
    public PaginationSearchResult findByModelOrMakeLike(int first, int max, boolean enableFilterPublished, String model) {
	PaginationSearchResult result = new PaginationSearchResult();
	DetachedCriteria criteria;

	if (enableFilterPublished) {
	    getHibernateTemplate().enableFilter(Car.PUBLISHED_FILTER);
	}

	// count tuples
	criteria = getFindByModelOrMakeLikeCriteria(model);
	criteria.setProjection(Projections.rowCount());
	result.setCount(DataAccessUtils.intResult(getHibernateTemplate().findByCriteria(criteria)));

	// get tuples
	criteria = getFindByModelOrMakeLikeCriteria(model);
	criteria.addOrder(DEFAULT_ORDER);
	result.setList(getHibernateTemplate().findByCriteria(criteria));

	return result;
    }

    /**
     * @see be.sc.classicnetwork.dao.CarDao#findImageById(int)
     */
    public Image findImageById(String id) {
	return (Image) getHibernateTemplate().get(Image.class, id);
    }

    /**
     * @see be.sc.classicnetwork.dao.CarDao#findContactsForNotification(int)
     */
    @SuppressWarnings("unchecked")
    public Collection<Contact> findContactsForNotification(String carId) {
	return getHibernateTemplate().find(
		"select contact from Contact contact, Car car "
			+ "where car.id=? and car.make in elements(contact.makes) "
			+ "and (car.site is null or car.site not in elements(contact.sites)) "
			+ "and contact.published = true", carId);
    }

}
